#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace LogModule;

const static int defaultsockfd = -1;
const static int backlog = 8;

// 服务器往往是禁止拷贝的
class TcpServer : public NoCopy
{
public:
    TcpServer(uint16_t port)
        : _port(port),
          _listensockfd(defaultsockfd),
          _isrunning(false)
    {
    }

    void Init()
    {
        // 捕捉SIGCHLD信号，进行该信号的忽略，这样父进程就不需要进行等待了
        // signal(SIGCHLD, SIG_IGN);

        // 1.创建套接字
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3

        // 2. bind绑定
        InetAddr local(_port);
        int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success" << _listensockfd; // 3

        // 3.设置socket状态为listen
        n = listen(_listensockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success" << _listensockfd; // 3
    }

    // 提供服务的方法
    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 先读取数据
            //  a. n>0：读取成功
            //  b. n<0：读取失败
            //  c. n==0：对端把连接关了，读到了文件的结尾\0
            // 这和管道很像
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0; // 设置为c风格的字符串，n<=sizeof(buffer)-1
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " say#" << buffer;

                // 2. 写回数据
                std::string echo_string = "echo# ";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << "退出了...";
                // 对端退出了，我也退出
                close(sockfd);
                break;
            }
            else
            {
                // 客户端读取异常了
                LOG(LogLevel::DEBUG) << peer.StringAddr() << "异常...";
                // 退出
                close(sockfd);
                break;
            }
        }
    }

    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // a. 获取连接
            struct sockaddr_in peer;
            memset(&peer, 0, sizeof(peer));
            socklen_t len = sizeof(peer);
            // 如果没有连接到来，accept就会阻塞
            int sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error,continue next";
                continue;
            }

            InetAddr addr(peer);

            LOG(LogLevel::INFO) << "accept success,fd peer addr: " << sockfd << " " << addr.StringAddr();

            // version0 ---test version——单进程程序——不会存在
            // Service(sockfd, addr);

            // version1 ---多进程版本
            pid_t id = fork();
            if (id < 0)
            {
                LOG(LogLevel::FATAL) << "fork error";
                exit(FOCK_ERR);
            }
            else if (id == 0)
            {
                // 子进程，子进程除了看到sockfd，能看到listensockfd吗？
                // 答案是可以的，但是我们不想让子进程访问listensock
                // 所以得让子进程关闭listensock
                close(_listensockfd);
                if (fork() > 0) // 让子进程创建孙子进程，然后自己退出
                {
                    exit(OK);
                }
                // 让孙子进程处理IO请求
                // 孙子进程由于子进程退出了，变成了孤儿进程，由系统回收
                Service(sockfd, addr);
                // 然后让子进程处理完任务后直接退出
                exit(OK);
            }
            else
            {
                // 父进程
                close(sockfd);

                // 父进程进行等待，这个时候由于我们让子进程退出了，所以不会阻塞
                pid_t rid = waitpid(id, nullptr, 0);
                (void)rid;
            }
        }

        _isrunning = false;
    }

    ~TcpServer() {}

private:
    uint16_t _port;
    int _listensockfd; // 监听套接字
    bool _isrunning;
};